package com.hjx.objectassert;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

public class ObjectUtil {

    private static final Map<String, Comparator> comparatorMappings = new HashMap<String, Comparator>();

    private static Comparator newComparator(String propName){

        return new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Object object1 = executeReadMethod(propName, o1);
                Object object2 = executeReadMethod(propName, o2);
                if(Objects.equals(object1, object2)) {
                    return 0;
                } else if(ObjectUtils.notEqual(object1, object2) && object1 == null) {
                    return -1;
                } else if(ObjectUtils.notEqual(object1, object2) && object2 == null) {
                    return 1;
                } else {
                    return ((Comparable)object1).compareTo(object2);
                }
            }

        };
    }

    static {
        comparatorMappings.put("deposits", newComparator("balance"));

        comparatorMappings.put("installments", newComparator("currentMonth"));

        comparatorMappings.put("shoppingSheets", newComparator("orderIndex"));

        comparatorMappings.put("allData", newComparator("fullCardNum"));

        comparatorMappings.put("bills", newComparator("billMonth"));
    }

    private static boolean inExcludeList(String fieldName) {
        String[] excludeList = new String[] {
                "cardId", "billId", "id"
        };
        for (String excludedFiledName : excludeList) {
            if (fieldName.equals(excludedFiledName)) {
                return true;
            }
        }
        return false;
    }


    public static <T> boolean equals(Class<T> actualType, Object except, Object actual){
        if (except == actual){
            return true;
        } else if (actual.getClass().equals(actualType)) {
            for (Field field : actualType.getDeclaredFields()){
                if (inExcludeList(field.getName())) {
                    continue;
                }

                if(field.getType().isAssignableFrom(List.class)){
                    Object exceptObjectMethodRetVal = executeReadMethod(field.getName(), except);
                    List exceptList = List.class.cast(exceptObjectMethodRetVal);
                    Objects.requireNonNull(exceptList).sort(comparatorMappings.get(field.getName()));

                    Object actualObjectMethodRetVal = executeReadMethod(field.getName(), actual);
                    List actualList = List.class.cast(actualObjectMethodRetVal);
                    Objects.requireNonNull(actualList).sort(comparatorMappings.get(field.getName()));

                    for (int i = 0 ; i < exceptList.size() ; i++){
                        if(!Objects.equals(exceptList.get(i), actualList.get(i))){
                            return false;
                        }
                    }

                } else {
                    Object exceptObjectMethodRetVal = executeReadMethod(field.getName(), except);
                    Object thisObjectMethodRetVal = executeReadMethod(field.getName(), actual);
                    if(!Objects.equals(exceptObjectMethodRetVal, thisObjectMethodRetVal)){
                        return false;
                    }
                }
            }
            return true;

        } else {
            return false;
        }

    }

    public static <T> String toString(Class<T> actualType, Object thisObject){
        for (Field field : actualType.getDeclaredFields()){
            if(field.getType().isAssignableFrom(List.class)){
                Object exceptObjectMethodRetVal = executeReadMethod(field.getName(), thisObject);
                List exceptList = List.class.cast(exceptObjectMethodRetVal);
                Objects.requireNonNull(exceptList).sort(comparatorMappings.get(field.getName()));
            }
        }
        return ToStringBuilder.reflectionToString(thisObject, ToStringStyle.MULTI_LINE_STYLE).replaceAll("@"+Long.toHexString(thisObject.hashCode()), "");
    }

    private static Object executeReadMethod(String propName, Object bean) {
        try {
            PropertyDescriptor pd = new PropertyDescriptor(propName, bean.getClass());
            Method method = pd.getReadMethod();
            return method.invoke(bean);
        } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}
